package com.lipaluma.field.mapper.single;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import com.lipaluma.context.Context;
import com.lipaluma.exceptions.CsvParserValidationException;
import com.lipaluma.field.adaptor.Adaptor;
import com.lipaluma.field.adaptor.AdaptorFactory;
import com.lipaluma.field.adaptor.ChainAdaptor;
import com.lipaluma.field.mapper.FieldAdapted;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.mapper.FieldValidated;
import com.lipaluma.field.mapper.mapping.FieldMappingInfo;
import com.lipaluma.field.validator.ChainValidator;
import com.lipaluma.field.validator.ValidationError;
import com.lipaluma.field.validator.Validator;
import com.lipaluma.xml.mapping.XmlFieldMapping;

/**
 * This class extends the {@link AbstractSingleFieldMapper} that describes an algorithm to marshall and unmarshall values.
 * <p>
 * This subclass implements {@link FieldAdapted} and {@link FieldValidated} to activate the adaptation of the extracted value and the validation
 * of the value parsed.
 * 
 * @See {@link AbstractSingleFieldMapper}
 * @author Mario
 *
 */
public abstract class AbstractStandardSingleFieldMapper extends AbstractSingleFieldMapper implements FieldValidated, FieldAdapted {

	protected ChainAdaptor adaptor;
	protected ChainValidator validator;

	public AbstractStandardSingleFieldMapper() {}
	public AbstractStandardSingleFieldMapper(FieldMappingInfo mapping) {
		super(mapping);
	}

	@Override
	public void addNewAdaptor(Adaptor adaptor) {
		this.adaptor.push(adaptor);
	}

	@Override
	public void initAdaptor(Context context, Field field) {
		this.adaptor = AdaptorFactory.createNewChainAdaptor(context, field);
	}
	
	@Override
	public void initAdaptor(Context context, Field field, XmlFieldMapping mapping) {
		this.adaptor = AdaptorFactory.createNewChainAdaptor(context, field, mapping);
	}
	
	@Override
	public void required(boolean required) {
		validator.setRequired(required);
	}
	
	@Override
	public void initValidator(Context context, Field field) {
		this.validator = createChainValidator(context, field);
		if(this.validator == null)
			this.validator = new ChainValidator(false);
	}
	
	/**
	 * init the Validator using annotations
	 * @param context TODO
	 * @param field field of the target object
	 * 
	 * @return the validator to be used for the {@link FieldMapper}
	 */
	protected abstract ChainValidator createChainValidator(Context context, Field field);

	@Override
	public void initValidator(Context context, Field field, XmlFieldMapping mapping) {
		this.validator = createChainValidator(context, field, mapping);
		if(this.validator == null)
			this.validator = new ChainValidator(mapping.isRequired());
	}
	
	/**
	 * init the Validator using xml mapping
	 * @param context TODO
	 * @param field field of the target object
	 * 
	 * @return the validator to be used for the {@link FieldMapper}
	 */
	protected abstract ChainValidator createChainValidator(Context context, Field field, XmlFieldMapping mapping);

	public String adaptValue(String value) {
		if(value == null)
			return null;
		if(adaptor != null)
			return adaptor.adapt(value);
		else return value;
	}

	public void addNewValidator(Validator validator) {
		this.validator.push(validator);
	}

	@Override
	public void validateValue(Object value) throws CsvParserValidationException {
		if(validator != null) {
			List<ValidationError> errors = new ArrayList<ValidationError>();
			if(!validator.validate(value, errors)) {
				StringBuilder message = new StringBuilder(getDescription());
				message.append("An error occured on validation of the value ").append(value);
				for (ValidationError error : errors) {
					message.append("\n").append(error.getMessage());
				}
				throw new CsvParserValidationException(message.toString());
			}
		}
	}

}
